//
//  BKHttpRequestManager.m
//  BCommon
//
//  Created by baboy on 4/1/15.
//  Copyright (c) 2015 baboy. All rights reserved.
//

#import "BKHttpRequestManager.h"
#import "BKKit.h"
#import "FileCache.h"
#import "NSString+x.h"
#import "NSDictionary+x.h"
#import "NSArray+x.h"
#import "NSData+x.h"
#import "HttpResponse.h"

@interface BKHttpRequestManager()
//@property (nonatomic, strong) BKHttpRequestQueue *requestQueue;
@end

@implementation BKHttpRequestManager
+ (id)lock {
    static id _lock = nil;
    static dispatch_once_t initOnceLock;
    dispatch_once(&initOnceLock, ^{
        _lock = [[NSRecursiveLock alloc] init];
    });
    return _lock;
}
+ (id)defaultManager {
    static id _defaultHttpRequestManager = nil;
    static dispatch_once_t initOnceHttpRequestManager;
    dispatch_once(&initOnceHttpRequestManager, ^{
        _defaultHttpRequestManager = [[BKHttpRequestManager alloc] init];
    });
    
    return _defaultHttpRequestManager;
}

+ (_Nonnull id)manager{
    return [[BKHttpRequestManager alloc] init];
}
- (id)init{
    if (self = [super init]) {
        self.responseSerializer.acceptableContentTypes = nil;
//        self.requestQueue = [[BKHttpRequestQueue alloc] init];
        self.requestSerializer = [AFJSONRequestSerializer serializer];
        self.requestSerializer.timeoutInterval = 90.0f;
    }
    return self;
}

- (BOOL)cancelTask:(BKHttpResponseHandler *_Nullable)task{
//    [self.requestQueue cancelTask:task];
    return YES;
}
- (void)setCacheRequestIgnoreParams:(NSArray *)cacheRequestIgnoreParams{
    _cacheRequestIgnoreParams = cacheRequestIgnoreParams;
}
- (NSString *)getRequestCachePath:(NSString *)url{
    NSString *u = url;
//    for (NSString *field in self.cacheRequestIgnoreParams) {
        
//        [str stringByReplacingOccurrencesOfString:@"^[A-Z]e" withString:@"mE" options:NSRegularExpressionSearch range:NSMakeRange(0, str.length)];
//
//        NSString *f = [field stringByReplacingOccurrencesOfRegex:@"\\*" withString:@"[0-9a-zA-Z_-]+"];
//        NSString *re = [NSString stringWithFormat:@"(%@=[^=&]{0,}&?)",f];
////        NSArray *arr = [u arrayOfCaptureComponentsMatchedByRegex:re];
//        u = [u stringByReplacingOccurrencesOfRegex:re withString:@""];
//    }
    NSString *fp = getHttpRequestCacheFilePath([NSURL URLWithString:u]);
    return fp;
}
/**
 *  覆盖父类私有方法
 */
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];
    
    return dataTask;
}

- (nullable id )GET:(nullable NSString *)URLString
             parameters:(nullable id)parameters
            cachePolicy:(BKHttpRequestCachePolicy)cachePolicy
                success:(nullable void (^)(id _Nonnull task, id _Nullable json))success
                failure:(nullable void (^)(id _Nullable task,id _Nullable json, NSError * _Nonnull error))failure{
    
    BKHttpRequestPool *pool = self.requestPool;
    @synchronized (pool) {
        NSString *taskId = URLString;
        BKHttpResponseHandler *responseHandler = [[BKHttpResponseHandler alloc] initWithTaskId:taskId UUID:[NSUUID UUID]];
        responseHandler.successBlock = success;
        responseHandler.failureBlock = failure;
        
        NSURLSessionTask *sessionTask = [pool sessionTaskForTaskId:taskId];
        if (sessionTask) {
            [pool addResponseHandler:responseHandler];
            return responseHandler;
        }

        //NSURLSessionDataTask
        sessionTask = [self dataTaskWithHTTPMethod:@"GET"
                                         URLString:URLString
                                        parameters:parameters
                                    uploadProgress:nil
                                  downloadProgress:nil
                                           success:^(NSURLSessionDataTask *dataTask, id json) {
            success(dataTask,json);
            if (cachePolicy & BKHttpRequestCachePolicyCached) {
                if (json) {
                    
                    NSString *fp = getHttpRequestCacheFilePath([NSURL URLWithString:[URLString URLStringWithParam:parameters]]);
                    [[json jsonData] writeToFile:fp atomically:YES];
                    DLOG(@"%@%lld",fp,[fp sizeOfFile]);
                }
            }
//            [self.session finishTasksAndInvalidate];
        }//end success block
                                                              failure:^(NSURLSessionDataTask *dataTask, NSError *error) {
            id json = nil;
            if (cachePolicy & BKHttpRequestCachePolicyFallbackToCacheIfLoadFails) {
                NSData *data = getHttpRequestCacheFileData([NSURL URLWithString:[URLString URLStringWithParam:parameters]]);
                if (data) {
                    DLOG(@"read cache:%@",getHttpRequestCacheFilePath([NSURL URLWithString:[URLString URLStringWithParam:parameters]]));
                    json = [data json];
                }
            }
            failure(dataTask,json,error);
//            [self.session finishTasksAndInvalidate];
        }];
        [pool addSessionTask:sessionTask withResponseHandler:responseHandler];
        return responseHandler;
    }
}
- (id)getJSON:(NSString *)URLString
   parameters:(id)parameters
  cachePolicy:(BKHttpRequestCachePolicy)cachePolicy
      success:(void (^)(id _Nonnull, id _Nullable))success
      failure:(void (^)(id _Nullable,id _Nullable, NSError * _Nonnull))failure
{
    BKHttpRequestPool *pool = self.requestPool;
    @synchronized(pool) {
        id p = parameters;
        if (![p isMemberOfClass:[NSDictionary class]] && [p respondsToSelector:@selector(dict)]) {
            p = [p dict];
        }
        
        NSString *url = [URLString URLStringWithParam: p];
        DLOG(@"url:%@", url);
        NSString *taskId = url;
        
        
        BKHttpResponseHandler *responseHandler = [[BKHttpResponseHandler alloc] initWithTaskId:taskId UUID:[NSUUID UUID]];
        responseHandler.successBlock = success;
        responseHandler.failureBlock = failure;
        
        NSURLSessionTask *sessionTask = [pool sessionTaskForTaskId:taskId];
        if (sessionTask) {
            [pool addResponseHandler:responseHandler];
            return responseHandler;
        }
        sessionTask = [self dataTaskWithHTTPMethod:@"GET"
                                         URLString:URLString
                                        parameters:parameters
                                    uploadProgress:nil
                                  downloadProgress:nil
                                           success:^(NSURLSessionDataTask * _Nullable dataTask, id _Nonnull json) {
            @synchronized(pool) {
                [pool clearTaskWithTaskId:taskId
                                  runWithBlock:^(BKHttpResponseHandler * _Nonnull item)  {
                                      item.successBlock(dataTask, json);
                                  }];
                if (cachePolicy & BKHttpRequestCachePolicyCached) {
                    if (json) {
                        NSString *fp = [self getRequestCachePath:url];
                        [[json jsonData] writeToFile:fp atomically:YES];
                    }
                }
//                [self.session finishTasksAndInvalidate];
            }
        }// end success block
                                           failure:^(NSURLSessionDataTask * _Nullable dataTask, NSError * _Nonnull error) {
            id json = nil;
            if (cachePolicy & BKHttpRequestCachePolicyFallbackToCacheIfLoadFails) {
                NSString *fp = [self getRequestCachePath:url];
                NSData *data = [fp fileData];
                if (data) {
                    DLOG(@"read cache:%@",getHttpRequestCacheFilePath([NSURL URLWithString:[URLString URLStringWithParam:parameters]]));
                    json = [data json];
                }
            }
            @synchronized(pool) {
                [pool clearTaskWithTaskId:taskId
                                  runWithBlock:^(BKHttpResponseHandler * _Nonnull item)  {
                                      item.failureBlock(dataTask,json,error);
                                  }];
//                [self.session finishTasksAndInvalidate];
            }
            
        }];
        [pool addSessionTask:sessionTask withResponseHandler:responseHandler];
        return responseHandler;
    }
}
- (id)getJSON:(NSString *)URLString
   parameters:(id)parameters
      success:(void (^)(id _Nonnull, id _Nullable))success
      failure:(void (^)(id _Nullable,id _Nullable, NSError * _Nonnull))failure
{
    return [self getJSON:URLString
              parameters:parameters
             cachePolicy:BKHttpRequestCachePolicyNone
                 success:success
                 failure:failure];
}
- (nullable id)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                             progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure{
    BKHttpRequestPool *pool = self.requestPool;
    @synchronized (pool) {
        NSString *taskId = [[NSUUID UUID] UUIDString];
        BKHttpResponseHandler *responseHandler = [[BKHttpResponseHandler alloc] initWithTaskId:taskId UUID:[NSUUID UUID]];
        
        NSURLSessionTask *sessionTask = [pool sessionTaskForTaskId:taskId];
        if (sessionTask) {
            [pool addResponseHandler:responseHandler];
            return responseHandler;
        }

        sessionTask = [self dataTaskWithHTTPMethod:@"POST"
                                                               URLString:URLString
                                                              parameters:parameters
                                                          uploadProgress:uploadProgress
                                                        downloadProgress:nil
                                                                 success:^(NSURLSessionDataTask *dataTask, id responseObject) {
            success(dataTask, responseObject);
            [pool clearTaskWithTaskId:taskId];
//            [self.session finishTasksAndInvalidate];
        }// end success block
                                                                 failure:^(NSURLSessionDataTask *dataTask, NSError *error) {
            failure(dataTask, error);
//            [self.session finishTasksAndInvalidate];
        }];
        [pool addSessionTask:sessionTask withResponseHandler:responseHandler];
        return responseHandler;
    }
}
#pragma Download
- (nullable id)download:(NSString *_Nullable)URLString
            cachePolicy:(BKHttpRequestCachePolicy)cachePolicy
               userInfo:(id _Nullable)userInfo
               progress:(nullable void (^)(NSProgress * _Nullable downloadProgress)) downloadProgressBlock
                success:(nullable void (^)(id _Nullable task, id _Nullable fp))success
                failure:(nullable void (^)(id _Nullable task,id _Nullable fp, NSError * _Nonnull error))failure{
    
    BKHttpRequestPool *pool = self.requestPool;
    @synchronized(pool) {
        if (cachePolicy & BKHttpRequestCachePolicyLoadIfNotCached) {
            NSString *fp = getHttpRequestCacheFilePath([NSURL URLWithString:URLString]);
            if ([fp sizeOfFile]>0) {
                success(nil,[NSURL fileURLWithPath:fp]);
                DLOG(@"download,read from cache:%lld,%@",[fp sizeOfFile],[fp lastPathComponent]);
                return nil;
            }
        }
        NSString *taskId = URLString;
        
        
        BKHttpResponseHandler *responseHandler = [[BKHttpResponseHandler alloc] initWithTaskId:taskId UUID:[NSUUID UUID]];
        responseHandler.successBlock = success;
        responseHandler.failureBlock = failure;
        [pool addResponseHandler:responseHandler];
        

        NSURLSessionTask *sessionTask = [pool sessionTaskForTaskId:taskId];
        if (sessionTask) {
            [pool addResponseHandler:responseHandler];
            return responseHandler;
        }
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:URLString]];
        sessionTask = [super downloadTaskWithRequest:request
                                            progress:downloadProgressBlock
                                         destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
            return [NSURL fileURLWithPath:getHttpRequestCacheFilePath([NSURL URLWithString:URLString])];
        }
                                   completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
            @synchronized(pool) {
                [pool clearTaskWithTaskId:taskId
                                 runWithBlock:^(BKHttpResponseHandler * _Nonnull item) {
                    if (error) {
                        item.failureBlock(nil,filePath,error);
                    }
                    if(!error){
                        item.successBlock(nil,filePath);
                    }
                }];
            }//end synchronized
        }];
        [pool addSessionTask:sessionTask withResponseHandler:responseHandler];
        return responseHandler;
    }
}
- (nullable id)download:(NSString *_Nullable)URLString
            cachePolicy:(BKHttpRequestCachePolicy)cachePolicy
               progress:(nullable void (^)(NSProgress * _Nullable downloadProgress)) downloadProgressBlock
                success:(nullable void (^)(id _Nonnull task, id _Nullable fp))success
                failure:(nullable void (^)(id _Nullable task,id _Nullable fp, NSError * _Nonnull error))failure{
    return [self download:URLString
              cachePolicy:cachePolicy
                 userInfo:nil
                 progress:downloadProgressBlock
        success:success
                  failure:failure];
}

- (nullable id)download:(NSString *_Nullable)URLString
               progress:(nullable void (^)(NSProgress * _Nullable downloadProgress)) downloadProgressBlock
                success:(nullable void (^)(id _Nonnull task, id _Nullable fp))success
                failure:(nullable void (^)(id _Nullable task,id _Nullable fp, NSError * _Nonnull error))failure{
    return [self download:URLString
              cachePolicy:BKHttpRequestCachePolicyLoadIfNotCached
                 progress:downloadProgressBlock
        success:success
                  failure:failure];
}
@end
